import java.awt.geom.Point2D;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class Quiver extends Named implements Cloneable {
	private final Set<Node> nodes = new HashSet<Node>();
	private final Configuration config = Configuration
			.getDefaultConfiguration();

	public Quiver() {
		setName();
	}
	public Quiver(String name) {
		setName(name);
	}

	public Object Clone() {
		Quiver quiver = new Quiver();
		Map<Node, Node> map = new HashMap<Node, Node>();
		for (Node node : getNodes()) {
			Point2D point = node.getPoint();
			Node clonedNode = quiver.addNode(point.getX(), point.getY());
			clonedNode.name = node.name;
			map.put(node, clonedNode);
		}
		for (Arrow arrow : getArrows()) {
			Arrow clonedArrow = quiver.addArrow(map.get(arrow.getFromNode()), map
					.get(arrow.getToNode()));
			clonedArrow.name = arrow.name;
		}
		return quiver;
	}

	public String toString() {
		return "Quiver " + name + " with " + nodes.size() + " nodes and "
				+ countArrows() + " arrows";
	}

	public Set<Arrow> getArrows() {
		Set<Arrow> arrows = new HashSet<Arrow>();
		for (Node node : nodes) {
			arrows.addAll(node.getArrows());
		}
		return arrows;
	}

	public int countArrows() {
		int count = 0;
		for (Arrow arrow : getArrows()) {
			count += arrow.getName().split(",").length;
		}
		return count;
	}
	
	public Arrow getArrow(String arrowName){
		for (Arrow arrow : getArrows()){
			for (String namePart : arrow.getName().split(",")){
				if (arrowName.equals(namePart)){
					return arrow;
				}
			}
		}
		return null;
	}
	
	public Arrow getArrow(Point2D point, Arrow exceptArrow) {
		for (Arrow arrow : getArrows()) {
			if (arrow != exceptArrow && arrow.isClose(point)) {
				return arrow;
			}
		}
		return null;
	}

	public Arrow getArrow(Point2D point) {
		for (Arrow arrow : getArrows()) {
			if (arrow.isClose(point)) {
				return arrow;
			}
		}
		return null;
	}

	public Node getNode(Point2D point, Node exceptNode) {
		for (Node node : nodes) {
			if (node != exceptNode && node.isClose(point)) {
				return node;
			}
		}
		return null;
	}

	public Node getNode(Point2D point) {
		for (Node node : nodes) {
			if (node.isClose(point)) {
				return node;
			}
		}
		return null;
	}

	public Node getNode(String name) {
		for (Node node : nodes) {
			if (name.equals(node.getName())) {
				return node;
			}
		}
		return null;
	}

	public Named getLabel(Point2D point) {
		for (Node node : nodes) {
			if (node.isCloseLabel(point)) {
				return node;
			}
		}
		for (Arrow arrow : getArrows()) {
			if (arrow.isCloseLabel(point)) {
				return arrow;
			}
		}
		return null;
	}

	public Node addNode(double x, double y) {
		Node node = new Node(x, y);
		nodes.add(node);
		return node;
	}

	public Arrow addArrow(Arrow arrow) {
		for (Arrow arrow2 : arrow.getFromNode().getArrows()) {
			if (arrow2.getFromNode() == arrow.getFromNode()
					&& arrow2.getToNode() == arrow.getToNode()) {
				arrow2.setName(arrow2.getName() + ",");
				return arrow2;
			}
		}
		if (arrow.getFromNode() != arrow.getToNode()) {
			for (Arrow arrow2 : arrow.getFromNode().getArrows()) {
				if (arrow2.getFromNode() == arrow.getToNode()
						&& arrow2.getToNode() == arrow.getFromNode()) {
					arrow.isBidirectional = true;
					arrow2.isBidirectional = true;
				}
			}
			arrow.getFromNode().addArrow(arrow);
			arrow.getToNode().addArrow(arrow);
		} else {
			arrow.getFromNode().addArrow(arrow);
		}
		return arrow;
	}

	public void removeArrow(Arrow arrow) {
		String[] nameParts = arrow.getName().split(",");
		if (nameParts.length == 1) {
			if (arrow.isBidirectional) {
				for (Arrow arrow2 : getArrows()) {
					if (arrow2.getFromNode() == arrow.getToNode()
							&& arrow2.getToNode() == arrow.getFromNode()) {
						arrow2.isBidirectional = false;
						break;
					}
				}
			}
			arrow.getFromNode().removeArrow(arrow);
			arrow.getToNode().removeArrow(arrow);
		} else {
			String name = "";
			for (int i=0; i<nameParts.length-1; i++){
				if (i != 0){
					name += ",";
				}
				name += nameParts[i];
			}
			arrow.setName(name);
		}
	}

	public Arrow addArrow(Node fromNode, Node toNode) {
		return addArrow(new Arrow(fromNode, toNode));
	}

	public Arrow addArrow(String fromNodeString, String toNodeString) {
		Node fromNode = getNode(fromNodeString);
		Node toNode = getNode(toNodeString);
		if (fromNode != null && toNode != null) {
			return addArrow(fromNode, toNode);
		}
		return null;
	}

	public void copyArrows(Node fromNode, Node toNode) {
		if (fromNode == toNode) {
			return;
		}
		Set<Arrow> oldArrows = new HashSet<Arrow>(fromNode.getArrows());
		for (Arrow arrow : oldArrows) {
			arrow.getFromNode().removeArrow(arrow);
			arrow.getToNode().removeArrow(arrow);
		}
		for (Arrow arrow : oldArrows) {
			if ((arrow.getFromNode() == fromNode && arrow.getToNode() == toNode)
					|| (arrow.getFromNode() == toNode && arrow.getToNode() == fromNode)) {
				continue;
			}
			if (arrow.getFromNode() == fromNode){ 
				arrow.setFromNode(toNode);
			}
			if (arrow.getToNode() == fromNode) {
				arrow.setToNode(toNode);
			}
			addArrow(arrow);
		}
	}

	public String toGAPCode() {
		String nodeString = "";
		int i = 1;
		for (Node node : nodes) {
			if (node.getName().isEmpty()) {
				node.setName("n" + i);
			}
			if (i != 1) {
				nodeString += ", ";
			}
			nodeString += "\"" + node.getName() + "\"";
			i++;
		}
		String arrowString = "";
		i = 1;
		for (Arrow arrow : getArrows()) {
			for (String namePart : arrow.getName().split(",")) {
				if (i != 1) {
					arrowString += ",";
				}
				arrowString += "[\"" + arrow.getFromNode().getName() + "\", \""
						+ arrow.getToNode().getName() + "\", \"" + namePart
						+ "\"]";
				i++;
			}
		}
		return String.format("%s := Quiver([%s], [%s])", name, nodeString,
				arrowString);
	}

	public static Quiver fromGAPCode(String gapCode) {
		Configuration config = Configuration.getDefaultConfiguration();
		Quiver quiver = new Quiver();
		Pattern p = Pattern
				.compile("((?:\\S+\\:=)?)Quiver\\(\\[(.*?)\\],\\[(.*?)\\]\\)");
		Matcher m = p.matcher(gapCode.replace(" ", "")); // should remove all
		// whitespace
		if (m.find()) {
			if (!m.group(1).isEmpty()) {
				quiver
						.setName(m.group(1).substring(0,
								m.group(1).length() - 2));
			}
			if (!m.group(2).isEmpty()) {
				for (String nodeString : m.group(2).split(",")) {
					Node node = quiver.addNode(50 + Math.random()
							* (config.windowWidth - 100), 50 + Math.random()
							* (config.windowHeight - 100));
					node.setNameUnsafe(nodeString.replace("\"", ""));
				}
			}
			if (!m.group(3).isEmpty()) {
				for (String arrowString : m.group(3).split("\\],\\[")) {
					arrowString = arrowString.replace("[", "").replace("]", "")
							.replace("\"", "");
					String[] tmp = arrowString.split(",");
					Arrow arrow = quiver.addArrow(tmp[0], tmp[1]);
					if (arrow != null) {
						arrow.setNameUnsafe(tmp[2]);
					}
				}
			}
		}
		return quiver;
	}

	public void removeNode(Node node) {
		nodes.remove(node);
		for (Arrow arrow : node.getArrows()) {
			if (arrow.getFromNode() != node) {
				arrow.getFromNode().removeArrow(arrow);
			}
			if (arrow.getToNode() != node) {
				arrow.getToNode().removeArrow(arrow);
			}
		}
	}

	public void snapToGrid() {
		for (Node node : nodes) {
			node.snapToGrid();
		}
	}

	public Set<Node> getNodes() {
		return nodes;
	}

	public void clear() {
		nodes.clear();
	}
	
	public String generateName(String preferredName) {
		if (preferredName == null || preferredName.isEmpty()) {
			preferredName = config.defaultQuiverName;
		}
		Set<Integer> used = new HashSet<Integer>();
		for (Quiver quiver : QuiverFrame.getInstance().getQuivers()) {
			String name = quiver.getName();
			if (name == null) {
				continue;
			}
			String[] tmp = name.split("_");
			if (tmp.length == 1) {
				if (tmp[0].equals(preferredName)) {
					used.add(0);
				}
			} else {
				int num;
				try {
					num = Integer.parseInt(tmp[tmp.length - 1]);
				} catch (NumberFormatException e) {
					continue;
				}
				if (num <= 0) {
					continue;
				}
				String origName = "";
				for (int i = 0; i < tmp.length - 1; i++) {
					origName += tmp[i];
				}
				if (origName.equals(preferredName)) {
					used.add(num);
				}
			}
		}
		if (!used.contains(0)) {
			return preferredName;
		}
		int i = 1;
		while (true) {
			if (!used.contains(i)) {
				return preferredName + "_" + i;
			}
			i++;
		}
	}
}
